use core::fmt;

pub struct CodeBuffer {
    indent: IndentTracker,
    lines: Vec<String>,
}

impl CodeBuffer {
    pub fn new(indent: String) -> Self {
        Self {
            indent: IndentTracker::new(indent),
            lines: Vec::new(),
        }
    }

    pub fn emit_line(&mut self, line: &str) {
        let mut buf = self.indent.get_indent();
        buf.push_str(line);
        self.lines.push(buf);
    }

    pub fn emit_fmt(&mut self, f: fmt::Arguments<'_>) {
        let ind = self.indent.get_indent();
        self.lines.push(format!("{ind}{f}"));
    }

    pub fn emit_space(&mut self) {
        self.lines.push(String::new());
    }

    pub fn push_indent(&mut self) {
        self.indent.push();
    }

    pub fn pop_indent(&mut self) {
        self.indent.pop();
    }

    pub fn finalize(self) -> String {
        let mut buf = String::new();
        self.lines.iter().for_each(|l| {
            buf.push_str(l);
            if !l.ends_with("\n") {
                buf.push_str("\n");
            }
        });
        buf
    }
}

pub struct IndentTracker {
    depth: u8,
    seq: String,
}

impl IndentTracker {
    pub fn new(seq: String) -> Self {
        Self { depth: 0, seq }
    }

    pub fn push(&mut self) {
        self.depth += 1;
    }

    pub fn pop(&mut self) {
        assert!(self.depth > 0, "indent bottomed out");
        self.depth -= 1;
    }

    pub fn get_indent(&self) -> String {
        self.seq.repeat(self.depth as usize)
    }
}
